﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TribesPSO
{
    /// <summary>
    /// A particle implementation that moves based on hyperspherical distributions.  This implementation is based on the
    /// 2003 paper "TRIBES, a Parameter Free Particle Swarm Optimizer" by Maurice Clerc
    /// </summary>
    public sealed class HypersphereParticle : Particle
    {
        /// <summary>
        /// Initializes a new Particle that moves based on hyperspherical distributions.  This particle will use 
        /// the default TribesPSO.HyperspaceRandom RNG to move
        /// </summary>
        /// <param name="objectiveFunction">The IObjectiveFunction the particle will solve to determine its "goodness"</param>
        /// <param name="initialPosition">The initial position of the particle in its search space</param>
        public HypersphereParticle(IObjectiveFunction objectiveFunction, EuclidianVector initialPosition)
            : base(objectiveFunction, initialPosition) { }

        /// <summary>
        /// Initializes a new Particle that moves based on hyperspherical distributions
        /// </summary>
        /// <param name="objectiveFunction">The IObjectiveFunction the particle will solve to determine its "goodness"</param>
        /// <param name="initialPosition">The initial position of the particle in its search space</param>
        /// <param name="randomNumberGenerator">The random number generator that the particle will use to move</param>
        public HypersphereParticle(
            IObjectiveFunction objectiveFunction,
            EuclidianVector initialPosition,
            IHyperspaceRandom randomNumberGenerator)
            : base(objectiveFunction, initialPosition, randomNumberGenerator) { }

        /// <summary>
        /// Computes the next position of a particle by creating a hypersphere around this particle's best solution and the best
        /// solution of its informers.  The new position is the center of gravity of two points chosen within these two hypersphers weighted
        /// by how good this particle's best solution is and how good the solution of its best informer is.
        /// 
        /// Furthermore, if a particle has not improved its best performance two times in a row, we'll add a bit of gaussian noise
        /// to the final position of the particle
        /// </summary>
        /// <param name="bestInformerSolution"></param>
        /// <returns></returns>
        protected override EuclidianVector CalculateNewPosition(Solution bestInformerSolution)
        {
            /*Begin with two points in the search space:  The particle's best perfomance P and the best of it's informers G*/
            EuclidianVector P = this.BestSolution.Position;
            EuclidianVector G = bestInformerSolution.Position;

            /* Consider two hyperspheres Hp and Hg centered on P and G with a radius equal to the distance between P and G 
             * Pick two points P' and G' based on a uniform distribution within their respective hyperspheres */
            double Radius = (P - G).Length;
            EuclidianVector Pprime = this.RandomNumberGenerator.NextUniformVector(P, Radius);
            EuclidianVector Gprime = this.RandomNumberGenerator.NextUniformVector(G, Radius);

            /*Weight each point based on the relative qualities of the solutions at P and G*/
            double PprimeWeight = bestInformerSolution.Error / (this.BestSolution.Error + bestInformerSolution.Error);
            double GprimeWeight = this.BestSolution.Error / (bestInformerSolution.Error + this.BestSolution.Error);

            /*Finally, compute the new position as the weighted center of gravity between Pprime and Gprime*/
            EuclidianVector NewPosition = EuclidianVector.CenterOfGravity(Pprime, PprimeWeight, Gprime, GprimeWeight);

            if (!this.IsExcellent)
            {
                double noiseStdev = Math.Abs(this.BestSolution.Error - bestInformerSolution.Error) / (this.BestSolution.Error + bestInformerSolution.Error);

                /* Here is the more readable, original, slower, version of the code.
                 * The uncommented code is about 17% faster according to the benchmarks*/
                //EuclidianVector Velocity = NewPosition - this.Position;
                //List<double> NewVelocityVectors = new List<double>();
                //for (int n = 0; n < Velocity.Dimensions; n++)
                //{
                //    NewVelocityVectors.Add(Velocity[n] * (1 + this.RandomNumberGenerator.NextGaussian(0, noiseStdev)));
                //}
                //return this.Position + new EuclidianVector(NewVelocityVectors);

                /* Faster version */
                EuclidianVectorBuilder NoisyNewPosition = new EuclidianVectorBuilder();
                for (int n = 0; n < NewPosition.Dimensions; n++)
                {
                    double NoisyVelocity = (NewPosition[n] - Position[n]) * (1 + this.RandomNumberGenerator.NextGaussian(0, noiseStdev));
                    NoisyNewPosition.AddDimensionData(this.Position[n] + NoisyVelocity);
                }
                NewPosition = NoisyNewPosition.ToEuclidianVector();
            }

            return NewPosition;
        }
    }
}
